﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Cofe.Core.Validater;
using System.IO;
using Cofe.Core.Utils;
using Cofe.Core;
using System.ComponentModel.DataAnnotations;
#if NETFX_CORE
using Windows.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Cofe.Core.Property;
#else
using System.Windows.Media.Imaging;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Cofe.Core.Property;
#endif

namespace Cofe.Core
{
    /// <summary>
    /// Represent a property, it's value type and value
    /// </summary>
    public class PropertyPair
    {
        #region Data

        private object _value = null;
        private object _property = null;
        private bool _valueLoaded = false;
        private Func<object> _valueFunc = null;
        private bool _isValid = true;
        public DateTime _valueRetrieveTime = DateTime.UtcNow;

        /// <summary>
        /// Cache value obtained from _valueFunc()
        /// </summary>
        private bool _cacheValue = true;

        #endregion

        #region Public Properties

        public int PropertyId
        {
            get
            {
                int propId = (int)Property;
                return propId;
            }
            protected set
            {
                var prop = CofeServices.PropertyDictionary.GetProperty(value);
                if (prop == null)
                    throw new ArgumentException("Unrecognized PropertyId " + value.ToString());
                Property = prop;
            }
        }

        /// <summary>
        /// Property key (e.g. FileSystemProperties.FullName)
        /// </summary>
        public object Property { get { return _property; } protected set { setProperty(value); } }
        /// <summary>
        /// Type of value.
        /// </summary>
        public Type Type { get; private set; }

        public string TypeString { get { return Type.Name; } }

        public Guid EntityId { get; set; }

        /// <summary>
        /// Value of value.
        /// </summary>
        public object Value { get { return getValue(); } set { setValue(value); } }
        public bool ValueIsLoaded { get { return _valueLoaded; } private set { _valueLoaded = value; } }
        public DateTime ValueRetrieveTime { get { return _valueRetrieveTime; } set { _valueRetrieveTime = value; } }

        public bool IsValid { get { if (!(ValueIsLoaded)) getValue(); return _isValid; } private set { _isValid = value; } }
        public bool HasValue { get { return Value != null; } }

        public Int32 ValueAsInt32 { get { return Value == null ? 0 : (Int32)Value; } }
        public Int64 ValueAsInt64 { get { return Value == null ? 0 : (Int64)Value; } }
        public UInt64 ValueAsUInt64 { get { return Value == null ? 0 : (UInt64)Value; } }
        public string ValueAsString { get { return Value == null ? "" : Value as String; } }
        public bool ValueAsBoolean { get { return Value == null ? false : (bool)Value; } }
        public DateTime ValueAsUtcDateTime { get { return Value == null ? CofeConstants.MinDateTime : (DateTime)Value; } }
        public DateTime ValueAsLocalDateTime { get { return Value == null ? CofeConstants.MinDateTime : ((DateTime)Value).ToLocalTime(); } }

        #endregion

        #region Methods

        public PropertyPair[] ToArray()
        {
            return new PropertyPair[] { this };
        }

        protected virtual object getValue()
        {
            if (!ValueIsLoaded)
            {
                try
                {
                    if (_cacheValue)
                        _value = _valueFunc();
                    else return _valueFunc();
                }
                catch (Exception ex)
                {
                    CofeServices.Logger.Log(ex);
                    _value = null;
                }
                ValueIsLoaded = true;
                IsValid = !(GetValidationResult().Any());
                ValueRetrieveTime = DateTime.UtcNow;
            }
            return _value;
        }

        protected virtual void setValue(object value)
        {
            //if (ValueIsLoaded)
            //    throw new InvalidOperationException("Value is already set.");
            _value = value;
            ValueIsLoaded = true;
            IsValid = !(GetValidationResult().Any());
        }

        private void setProperty(object property)
        {
            //if (_property != null)
            //    throw new InvalidOperationException("Property is already set.");
            _value = null;
            ValueIsLoaded = false;
            _property = property;
            Type = PropertyTypeExtension.GetDefaultPropertyType(property);
        }

        public T ValueAs<T>()
        {
            if (Value == null)
                return default(T);
            try
            {
                return (T)Value;
            }
            catch
            {
                return default(T);
            }
        }

        public override string ToString()
        {
            return string.Format("({2}){0} = {1}", Property, Value, this.GetType().Name);
        }

        public Stream ToMemoryStream(out MediaType mediaType)
        {
            MemoryStream ms = new MemoryStream();
            SaveToStream(ms, out mediaType);
            ms.Seek(0, SeekOrigin.Begin);
            return ms;
        }

        /// <summary>
        /// Save the input as stream.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="mediaType"></param>
        public void SaveToStream(Stream stream, out MediaType mediaType)
        {
            mediaType = MediaType.Stream;
            if (Type == typeof(BitmapSource))
            {
                throw new NotImplementedException();
                //(Value as BitmapSource).SaveAsPNGStream(stream);
                mediaType = MediaType.Png;
            }
            else
            {
                using (StreamWriter sw = new StreamWriter(stream))
                {
                    sw.Write(Value);
                    sw.Flush();
                }
            }
        }

        public IEnumerable<ValidationResult> GetValidationResult()
        {
            return IPropertyValidaterExtension.GetValidationResult(this.Property, this.Value);
        }

        #endregion

        #region Constructor

        private static PropertyPair constructContainer(object property)
        {
            Type propertyType = DecoratorExtension.GetDefaultType(property);
            switch (propertyType.ToString())
            {
                case "System.String" : return new PropertyPairString();
                case "System.Double": return new PropertyPairDouble();
                case "System.Int16": return new PropertyPairInt16();
                case "System.Int32": return new PropertyPairInt32();
                case "System.Int64": return new PropertyPairInt64();
                case "System.Boolean": return new PropertyPairBool();
                case "System.DateTime": return new PropertyPairDateTime();
                case "System.Guid": return new PropertyPairGuid();
                case "System.Byte[]": return new PropertyPairByteArray();
            }            
            return new PropertyPair();
        }

        public static PropertyPair FromValueFunc(object property, Func<object> valueFunc, bool loadImmediately, bool allowCacheValue)
        {
            if (loadImmediately)
                return FromValue(property, valueFunc());

            PropertyPair retVal = constructContainer(property);
            retVal._cacheValue = allowCacheValue;
            retVal.Property = property;
            retVal._valueFunc = valueFunc;
            return retVal;
        }

        public static PropertyPair FromValue(object property, object value, DateTime valueRetrieveTime)
        {
            if (value is PropertyPair)
                throw new ArgumentException("value");

            PropertyPair retVal = constructContainer(property);
            retVal.Property = property;
            retVal.Value = value;
            retVal.ValueRetrieveTime = valueRetrieveTime;

            return retVal;
        }

        public static PropertyPair FromValue(object property, object value)
        {
            return FromValue(property, value, DateTime.UtcNow);
        }

        //private PropertyPair(object property, Func<object> valueFunc, bool loadImmediately)
        //{
        //    Property = property;

        //    //Type dataType = Enum.GetUnderlyingType(property.GetType());            

        //    if (loadImmediately)
        //        Value = valueFunc();
        //    else _valueFunc = valueFunc;

        //    ////Compare value type to declared type in Attribute
        //    //IPropertyValidaterExtension.VerifyValue(property, value);
        //}

        //public PropertyPair(object property, object value)
        //    : this(property, () => value, true)
        //{
        //}     

        protected PropertyPair()
        {
        }


        #endregion

        public override bool Equals(object obj)
        {
            //if (this.Property == null || this.Value == null)
            //    return false;

            if (obj is PropertyPair)
                return Property.Equals((obj as PropertyPair).Property) &&
                    (Value == (obj as PropertyPair).Value || Value.Equals((obj as PropertyPair).Value));
            return false;
        }

        public override int GetHashCode()
        {
            return Property.GetHashCode() + ValueRetrieveTime.GetHashCode();
        }

        private static PropertyPair combine(PropertyPairUtils.OpType opType, PropertyPair first, PropertyPair second)
        {
            if (!(first.Property.Equals(second.Property)))
                throw new NotSupportedException("Different property.");

            var prop = first.Property;
            var type = PropertyTypeExtension.GetDefaultPropertyType(prop);

            if (opType == PropertyPairUtils.OpType.Add && type == typeof(string))
                return PropertyPair.FromValue(prop, first.ValueAsString + "," + second.ValueAsString);

            var pTypeAttribute = AttributeUtils<PropertyTypeAttribute>.FindAttribute(first.Property);
            var invokeBehaviors = AttributeUtils<InvokeBehaviorAttribute>.FindAllAttributes(first.Property).ToArray();
            return PropertyPair.FromValue(first.Property, PropertyPairUtils.Combine(opType,
                pTypeAttribute, invokeBehaviors, first.Value, second.Value));
        }

        public static PropertyPair operator |(PropertyPair first, PropertyPair second)
        {
            return combine(PropertyPairUtils.OpType.Or, first, second);
        }

        public static PropertyPair operator +(PropertyPair first, PropertyPair second)
        {
            return combine(PropertyPairUtils.OpType.Add, first, second);
        }



    }
}
